package com.project.taohaoCoupons.merchant.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.starter.annotation.LogRecord;
import com.project.taohaoCoupons.framework.exception.ClientException;
import com.project.taohaoCoupons.framework.exception.ServiceException;
import com.project.taohaoCoupons.merchant.admin.common.constant.MerchantAdminRedisConstant;
import com.project.taohaoCoupons.merchant.admin.common.context.UserContext;
import com.project.taohaoCoupons.merchant.admin.common.enums.CouponTemplateStatusEnum;
import com.project.taohaoCoupons.merchant.admin.dao.entity.CouponTemplateDO;
import com.project.taohaoCoupons.merchant.admin.dao.mapper.CouponTemplateMapper;
import com.project.taohaoCoupons.merchant.admin.dto.req.CouponTemplateNumberReqDTO;
import com.project.taohaoCoupons.merchant.admin.dto.req.CouponTemplateSaveReqDTO;
import com.project.taohaoCoupons.merchant.admin.dto.resp.CouponTemplateQueryRespDTO;
import com.project.taohaoCoupons.merchant.admin.mq.event.CouponTemplateDelayEvent;
import com.project.taohaoCoupons.merchant.admin.mq.producer.CouponTemplateDelayExecuteStatusProducer;
import com.project.taohaoCoupons.merchant.admin.service.CouponTemplateService;
import com.project.taohaoCoupons.merchant.admin.service.basics.chain.MerchantAdminChainContext;
import lombok.RequiredArgsConstructor;
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.project.taohaoCoupons.merchant.admin.common.constant.CouponTemplateConstant.CREATE_COUPON_TEMPLATE_LOG_CONTENT;
import static com.project.taohaoCoupons.merchant.admin.common.constant.CouponTemplateConstant.TERMINATE_COUPON_TEMPLATE_LOG_CONTENT;
import static com.project.taohaoCoupons.merchant.admin.common.constant.CouponTemplateConstant.INCREASE_NUMBER_COUPON_TEMPLATE_LOG_CONTENT;
import static com.project.taohaoCoupons.merchant.admin.common.enums.ChainBizMarkEnum.MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY;
@Service
@RequiredArgsConstructor
public class CouponTemplateServiceImpl extends ServiceImpl<CouponTemplateMapper, CouponTemplateDO> implements CouponTemplateService {
    private  final MerchantAdminChainContext merchantAdminChainContext;
    private final CouponTemplateMapper couponTemplateMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final CouponTemplateDelayExecuteStatusProducer couponTemplateDelayExecuteStatusProducer;
    private final RBloomFilter<String> bloomFilter;
    @LogRecord(
            success = CREATE_COUPON_TEMPLATE_LOG_CONTENT,
            type = "CouponTemplate",
            bizNo = "{{#bizNo}}",
            extra = "{{#requestParam.toString()}}"
    )
    @Override
    public void createCouponTemplate(CouponTemplateSaveReqDTO requestParam) {
        //责任链校验参数
        merchantAdminChainContext.handle(MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY.name(), requestParam);

        //创建优惠券到数据库
        CouponTemplateDO couponTemplateDO = BeanUtil.toBean(requestParam, CouponTemplateDO.class);
        couponTemplateDO.setStatus(CouponTemplateStatusEnum.ACTIVE.getStatus());
        couponTemplateDO.setShopNumber(UserContext.getShopNumber());
        couponTemplateMapper.insert(couponTemplateDO);

        // 因为模板 ID 是运行中生成的，@LogRecord 默认拿不到，所以需要将其放在日志记录的上下文中，其会自动通过SpEL表达式获取对应的值。
        LogRecordContext.putVariable("bizNo", couponTemplateDO.getId());

        // 缓存预热：通过将数据库的记录序列化成 JSON 字符串放入 Redis 缓存
        CouponTemplateQueryRespDTO couponTemplateQueryRespDTO = BeanUtil.toBean(couponTemplateDO, CouponTemplateQueryRespDTO.class);
        Map<String, Object> stringObjectMap = BeanUtil.beanToMap(couponTemplateQueryRespDTO, false, true);
        Map<String, String> stringMap = stringObjectMap.entrySet().stream().collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> entry.getValue() != null ? entry.getValue().toString() : ""
        ));
        String key = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, couponTemplateDO.getId());
        // 通过 LUA 脚本执行设置 Hash 数据以及设置过期时间
        String luaScript = "redis.call('HMSET', KEYS[1], unpack(ARGV, 1, #ARGV - 1)) " +
                "redis.call('EXPIREAT', KEYS[1], ARGV[#ARGV])";
        List<String> keys = Collections.singletonList(key);
        List<String> args = new ArrayList<>(stringMap.size() * 2 + 1);
        stringMap.forEach((k, v) -> {
            args.add(k);
            args.add(v);
        });

        // 优惠券活动过期时间转换为秒级别的 Unix 时间戳
        args.add(String.valueOf(couponTemplateDO.getValidEndTime().getTime()/1000));
        stringRedisTemplate.execute(
                new DefaultRedisScript<>(luaScript,Long.class),
                keys,
                args.toArray()
        );
        //       发送延时消息事件，优惠券活动到期修改优惠券模板状态
        CouponTemplateDelayEvent event = CouponTemplateDelayEvent.builder()
                .shopNumber(UserContext.getShopNumber())
                .couponTemplateId(couponTemplateDO.getId())
                .delayTime(couponTemplateDO.getValidEndTime().getTime())
                .build();
        // 发送延时消息
        couponTemplateDelayExecuteStatusProducer.sendMessage(event);

        // 添加优惠券模板 ID 到布隆过滤器
        bloomFilter.add(String.valueOf(couponTemplateDO.getId()));
    }

    @Override
    public CouponTemplateQueryRespDTO findCouponTemplateById(String couponTemplateId) {
        LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
                .eq(CouponTemplateDO::getId, couponTemplateId);
        CouponTemplateDO one = couponTemplateMapper.selectOne(queryWrapper);
        return BeanUtil.toBean(one, CouponTemplateQueryRespDTO.class);
    }

    @LogRecord(
            success = TERMINATE_COUPON_TEMPLATE_LOG_CONTENT,
            type = "CouponTemplate",
            bizNo = "{{#couponTemplateId}}"
    )
    @Override
    public void terminateCouponTemplate(String couponTemplateId) {
//        验证是否存在数据横向越权
        LambdaQueryWrapper<CouponTemplateDO> queryWrapper= Wrappers.lambdaQuery(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
                .eq(CouponTemplateDO::getId, couponTemplateId);
        CouponTemplateDO one = couponTemplateMapper.selectOne(queryWrapper);
        if (one == null) {
            throw new ClientException("优惠券不存在");
        }
        // 验证优惠券是否已过期
        if(ObjectUtil.notEqual(one.getStatus(), CouponTemplateStatusEnum.ACTIVE.getStatus())) {
            throw new ClientException("优惠券已过期");
        }
        //记录优惠券修改前的数据
        LogRecordContext.putVariable("originalData", JSON.toJSONString(one));

        //修改优惠券状态
        CouponTemplateDO update = new CouponTemplateDO();
        update.setStatus(CouponTemplateStatusEnum.ENDED.getStatus());
        Wrapper<CouponTemplateDO> updateWrapper = Wrappers.lambdaUpdate(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
                .eq(CouponTemplateDO::getId, one.getId());
        couponTemplateMapper.update(update, updateWrapper);

        // 修改优惠券模板缓存状态为结束状态
        String key = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, one.getId());
        stringRedisTemplate.opsForHash().put(key, "status", String.valueOf(CouponTemplateStatusEnum.ENDED.getStatus()));
    }
    @LogRecord(
            success = INCREASE_NUMBER_COUPON_TEMPLATE_LOG_CONTENT,
            type = "CouponTemplate",
            bizNo = "{{#requestParam.couponTemplateId}}"
    )
    @Override
    public void increaseNumberCouponTemplate(CouponTemplateNumberReqDTO requestParam) {
        // 验证是否存在数据横向越权
        LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
                .eq(CouponTemplateDO::getId, requestParam.getCouponTemplateId());
        CouponTemplateDO couponTemplateDO = couponTemplateMapper.selectOne(queryWrapper);
        if (couponTemplateDO == null) {
            // 一旦查询优惠券不存在，基本可判定横向越权，可上报该异常行为，次数多了后执行封号等处理
            throw new ClientException("优惠券模板异常，请检查操作是否正确...");
        }

        // 验证优惠券模板是否正常
        if (ObjectUtil.notEqual(couponTemplateDO.getStatus(), CouponTemplateStatusEnum.ACTIVE.getStatus())) {
            throw new ClientException("优惠券模板已结束");
        }

        // 记录优惠券模板修改前数据
        LogRecordContext.putVariable("originalData", JSON.toJSONString(couponTemplateDO));

        // 设置数据库优惠券模板增加库存发行量
        int increased = couponTemplateMapper.increaseNumberCouponTemplate(UserContext.getShopNumber(), requestParam.getCouponTemplateId(), requestParam.getNumber());
        if (!SqlHelper.retBool(increased)) {
            throw new ServiceException("优惠券模板增加发行量失败");
        }

        // 增加优惠券模板缓存库存发行量
        String couponTemplateCacheKey = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, requestParam.getCouponTemplateId());
        stringRedisTemplate.opsForHash().increment(couponTemplateCacheKey, "stock", requestParam.getNumber());
    }

}
